Skip to content

Method: getDematPositionsByTokenUnsafe(long[], int)

1: package de.fhdw.gaming.ipspiel23.c4.domain.impl;
2:
3: import java.util.Set;
4:
5: import de.fhdw.gaming.ipspiel23.c4.collections.IReadOnlyDictionary;
6: import de.fhdw.gaming.ipspiel23.c4.domain.IC4BoardSlim;
7: import de.fhdw.gaming.ipspiel23.c4.domain.IC4Player;
8: import de.fhdw.gaming.ipspiel23.c4.domain.IC4SolutionSlim;
9: import de.fhdw.gaming.ipspiel23.c4.domain.C4PositionMaterializer;
10: import de.fhdw.gaming.ipspiel23.c4.domain.impl.evaluation.C4SolutionEvaluator;
11:
12: /**
13: * The lightweight internal representation of the connect four board, somewhat optimized for performance.
14: */
15: public class C4BoardSlim implements IC4BoardSlim {
16:
17: /**
18: * The empty token value.
19: */
20: public static final int EMPTY_TOKEN = 0;
21:
22: /**
23: * The number of rows in the board.
24: */
25: private final int rowCount;
26:
27: /**
28: * The number of columns in the board.
29: */
30: private final int columnCount;
31:
32: /**
33: * The flattened one-dimensional board state/token array.
34: */
35: private final int[] board;
36:
37: /**
38: * A lookup table for players by their token.
39: */
40: private final IReadOnlyDictionary<Integer, IC4Player> players;
41:
42: /**
43: * The number of tokens in a row required to win.
44: */
45: private final int solutionSize;
46:
47: /**
48: * The solution evaluator.
49: */
50: private final C4SolutionEvaluator evaluator;
51:
52: /**
53: * Creates a new board with the specified dimensions and solution size.
54: * @param players the players
55: * @param rowCount the number of rows
56: * @param columnCount the number of columns
57: * @param solutionSize the number of tokens in a row required to win
58: */
59: public C4BoardSlim(final IReadOnlyDictionary<Integer, IC4Player> players, final int rowCount,
60: final int columnCount, final int solutionSize) {
61: this.players = players;
62: this.rowCount = rowCount;
63: this.columnCount = columnCount;
64: this.board = new int[rowCount * columnCount];
65: this.solutionSize = solutionSize;
66: this.evaluator = new C4SolutionEvaluator(this);
67: }
68:
69: /**
70: * Creates a new board with the specified dimensions and solution size. Literally only used for unit testing :P
71: * @param board the board state
72: * @param players the players
73: * @param rowCount the number of rows
74: * @param columnCount the number of columns
75: * @param solutionSize the number of tokens in a row required to win
76: */
77: // we only use this ctor for unit testing to, yes directly inject an array in this class.
78: // It's just easier this way. So this is justified. Also: this ctor is not public, so all
79: // is fine :)
80: @SuppressWarnings("PMD.ArrayIsStoredDirectly")
81: public C4BoardSlim(final int[] board, final IReadOnlyDictionary<Integer, IC4Player> players, final int rowCount,
82: final int columnCount, final int solutionSize) {
83: this.board = board;
84: this.players = players;
85: this.rowCount = rowCount;
86: this.columnCount = columnCount;
87: this.solutionSize = solutionSize;
88: this.evaluator = new C4SolutionEvaluator(this);
89: }
90:
91: /**
92: * Copy constructor.
93: * @param original the original board
94: */
95: private C4BoardSlim(final C4BoardSlim original) {
96: this.players = original.players;
97: this.rowCount = original.rowCount;
98: this.columnCount = original.columnCount;
99: this.board = original.board.clone();
100: this.solutionSize = original.solutionSize;
101: this.evaluator = new C4SolutionEvaluator(this);
102: }
103:
104: @Override
105: public int emptyToken() {
106: return EMPTY_TOKEN;
107: }
108:
109: @Override
110: public int getRowCount() {
111: return this.rowCount;
112: }
113:
114: @Override
115: public int getColumnCount() {
116: return this.columnCount;
117: }
118:
119: @Override
120: @SuppressWarnings("PMD.MethodReturnsInternalArray")
121: // Justification: the whole purpose of this method is to provide direct
122: // access to the internal board, to allow users of this method to implement
123: // their own logic with as little overhead as possible.
124: // One possible use case could include creating bitmaps directly from this
125: // integer array, for example.
126: // The documentation of this method literally states that it should be used
127: // with care and it has "unsafe" in it's name, so yeah, by contract callers
128: // shouldn't change random things on the internal board :)
129: public int[] getBoardStateUnsafe() {
130: return this.board;
131: }
132:
133: @Override
134: public int getMinimumSolutionSize() {
135: return this.solutionSize;
136: }
137:
138: @Override
139: public IC4BoardSlim deepCopy() {
140: return new C4BoardSlim(this);
141: }
142:
143: @Override
144: public void updateTokenUnsafe(final int rowIndex, final int columnIndex, final int value) {
145: // this is a primarily internal API, so no custom bounds checks (performance)
146: // for example this API allows indexing out of bounds columns (as long as the row index
147: // isn't too high)
148: // also: java does bounds checks automatically anyways, so it's not like we're risking
149: // buffer overflows or raw memory access here :)
150: this.board[rowIndex * this.columnCount + columnIndex] = value;
151: }
152:
153: @Override
154: public IC4Player getPlayerByToken(final int fieldState) {
155: return this.players.getValueOrDefault(fieldState);
156: }
157:
158: @Override
159: public int getTokenUnsafe(final int rowIndex, final int columnIndex) {
160: return this.board[rowIndex * this.columnCount + columnIndex];
161: }
162:
163: @Override
164: public IC4SolutionSlim tryFindFirstSolution(final boolean updateCache) {
165: return this.evaluator.tryFindFirstSolution(updateCache);
166: }
167:
168: @Override
169: public IC4SolutionSlim tryFindFirstSolution() {
170: return this.evaluator.tryFindFirstSolution(true);
171: }
172:
173: @Override
174: public Set<IC4SolutionSlim> findAllSolutions(final boolean updateCache) {
175: return this.evaluator.findAllSolutions(updateCache);
176: }
177:
178: @Override
179: public Set<IC4SolutionSlim> findAllSolutions() {
180: return this.evaluator.findAllSolutions(true);
181: }
182:
183: @Override
184: public void resetSolutionAnalyzerCaches() {
185: this.evaluator.resetAnalyzerCaches();
186: }
187:
188: @Override
189: public boolean isEmptyUnsafe(final int row, final int column) {
190: return getTokenUnsafe(row, column) == EMPTY_TOKEN;
191: }
192:
193: @Override
194: public boolean isSolidUnsafe(final int row, final int column) {
195: if (this.checkBounds(row, column)) {
196: return !this.isEmptyUnsafe(row, column);
197: } else {
198: // the ground is also solid
199: return this.checkBounds(row - 1, column);
200: }
201: }
202:
203: @Override
204: public boolean checkBounds(final int row, final int column) {
205: // full check here (including lower bounds)
206: return row >= 0 && row < this.rowCount && column >= 0 && column < this.columnCount;
207: }
208:
209: @Override
210: public int getDematPositionsByTokenUnsafe(final long[] buffer, final int token) {
211: int index = 0;
212:• for (int row = 0; row < this.rowCount; row++) {
213:• for (int col = 0; col < this.columnCount; col++) {
214:• if (this.getTokenUnsafe(row, col) == token) {
215: // mitigate need for heap allocation by dematerialization to simple value type
216: buffer[index] = C4PositionMaterializer.dematerialize(row, col);
217: index++;
218: }
219: }
220: }
221: return index;
222: }
223:
224: @Override
225: public int getLegalDematPositionsUnsafe(long[] buffer) {
226: int index = 0;
227: boolean inAir = false;
228: for (int col = 0; col < this.columnCount; col++) {
229: int row = this.rowCount - 1;
230: for (; row >= 0 && !inAir; row--) {
231: inAir = this.getTokenUnsafe(row, col) == EMPTY_TOKEN;
232: }
233: if (inAir) {
234: inAir = false;
235: // mitigate need for heap allocation by dematerialization to simple value type
236: // row + 1 due to the last decrement in the loop
237: buffer[index] = C4PositionMaterializer.dematerialize(row + 1, col);
238: index++;
239: }
240: }
241: return index;
242: }
243:
244: @Override
245: public boolean isFull() {
246: for (int col = 0; col < this.columnCount; col++) {
247: if (this.board[col] == EMPTY_TOKEN) {
248: return false;
249: }
250: }
251: return true;
252: }
253:
254: @Override
255: public String toString() {
256: final StringBuilder bobTheBuilder = new StringBuilder(64);
257: // Can we fix it? No, we're using Java.
258: // BUT: we can at least write pretty toStrings :)
259: // I mean, as pretty as an integer array is gonna get ¯\_(ツ)_/¯
260: bobTheBuilder.append("C4BoardSlim[").append(this.getRowCount())
261: .append('x').append(this.getColumnCount()).append("]{\n");
262: for (int row = 0; row < this.getRowCount(); row++) {
263: bobTheBuilder.append(" ");
264: for (int col = 0; col < this.getColumnCount(); col++) {
265: bobTheBuilder.append(this.getTokenUnsafe(row, col)).append(' ');
266: }
267: bobTheBuilder.append('\n');
268: }
269: bobTheBuilder.append('}');
270: return bobTheBuilder.toString();
271: }
272: }